/******************************************************************************* * Copyright (c) 2002, 2015 GEBIT Gesellschaft fuer EDV-Beratung * und Informatik-Technologien mbH, * Berlin, Duesseldorf, Frankfurt (Germany) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * GEBIT Gesellschaft fuer EDV-Beratung und Informatik-Technologien mbH - initial API and implementation * IBM Corporation - bug fixes * John-Mason P. Shackelford (john-mason.shackelford@pearson.com) - bug 49445 * Ericsson AB, Hamdan Msheik - Bug 389564 * Ericsson AB, Julian Enoch - Bug 389564 * David North - Bug 475839 *******************************************************************************/ package org.eclipse.ant.internal.ui.model; import java.io.IOException; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.util.ArrayList; import java.util.List; import org.eclipse.ant.internal.core.IAntCoreConstants; import org.eclipse.ant.internal.ui.AntImageDescriptor; import org.eclipse.ant.internal.ui.AntUIImages; import org.eclipse.ant.internal.ui.AntUIPlugin; import org.eclipse.ant.internal.ui.AntUtil; import org.eclipse.ant.internal.ui.IAntUIConstants; import org.eclipse.core.resources.IFile; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.Path; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.URIUtil; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.IRegion; import org.eclipse.swt.graphics.Image; import com.ibm.icu.text.MessageFormat; /** * General representation of an Ant buildfile element. * */ public class AntElementNode implements IAdaptable, IAntElement { /** * The offset of the corresponding source. * * @see #getOffset() */ protected int fOffset = -1; /** * The length of the corresponding source. * * @see #getLength() */ protected int fLength = -1; /** * The length of the source to select for this node */ protected int fSelectionLength; /** * The parent node. */ protected AntElementNode fParent; /** * The import node that "imported" this element */ private AntElementNode fImportNode; /** * The child nodes. */ protected List<IAntElement> fChildNodes = null; /** * The (tag-)name of the element. */ protected String fName; /** * Whether this element has been generated as part of an element hierarchy this has problems. This is the severity of the problem. * * @see XMLProblem#NO_PROBLEM * @see XMLProblem#SEVERITY_ERROR * @see XMLProblem#SEVERITY_WARNING * @see XMLProblem#SEVERITY_FATAL_ERROR */ private int fProblemSeverity = AntModelProblem.NO_PROBLEM; private String fProblemMessage = null; /** * The absolute file system path of the file this element is defined within. */ private String fFilePath; /** * Whether this element has been generated from an external entity definition */ private boolean fIsExternal = false; /** * The unique (in the corresponding element tree) path of this element. */ private String fElementPath; /** * The (not necessarily unique) identifier of this element. */ private String fElementIdentifier; /** * The problem associated with this node. May be <code>null</code>. */ private IProblem fProblem; /** * The unique index of this element in it's parents child collection */ private int fIndex = 0; /** * Only used when opening an import element to indicate the location in the imported file */ private int fLine; private int fColumn; /** * Creates an instance with the specified name. */ public AntElementNode(String aName) { fName = aName; } public AntElementNode() { } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getName() */ @Override public String getName() { return fName; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getLabel() */ @Override public String getLabel() { return getName(); } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getChildNodes() */ @Override public List<IAntElement> getChildNodes() { return fChildNodes; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getParentNode() */ @Override public IAntElement getParentNode() { return fParent; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getProjectNode() */ @Override public AntProjectNode getProjectNode() { IAntElement projectParent = getParentNode(); while (projectParent != null && !(projectParent instanceof AntProjectNode)) { projectParent = projectParent.getParentNode(); } return (AntProjectNode) projectParent; } /** * Adds the specified element as a child. * <P> * The specified element will have this assigned as its parent. */ public void addChildNode(AntElementNode childElement) { childElement.setParent(this); synchronized (this) { if (fChildNodes == null) { fChildNodes = new ArrayList<>(); } fChildNodes.add(childElement); childElement.setIndex(fChildNodes.size() - 1); } } private void setIndex(int index) { fIndex = index; } protected void setParent(AntElementNode node) { fParent = node; } /** * Sets the absolute file system path of the file this element is defined within. */ public void setFilePath(String path) { if (path == null) { return; } URL url = null; try { url = new URL(path); } catch (MalformedURLException e) { fFilePath = path; return; } try { URL fileURL = FileLocator.toFileURL(url); // To be worked in 4.6 via Bug 476266 if (IAntCoreConstants.FILE.equals(fileURL.getProtocol())) { fFilePath = new Path((URIUtil.toFile(URIUtil.toURI(fileURL))).getAbsolutePath()).toString(); } } catch (URISyntaxException e) { AntUIPlugin.log(e); } catch (IOException e) { AntUIPlugin.log(e); } } /** * Returns the absolute file system path of the file this element is defined within. Only relevant for nodes that are external * * @see #isExternal() */ public String getFilePath() { return fFilePath; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getOffset() */ @Override public int getOffset() { return fOffset; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#setOffset(int) */ @Override public void setOffset(int anOffset) { fOffset = anOffset; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getLength() */ @Override public int getLength() { return fLength; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#setLength(int) */ @Override public void setLength(int aLength) { fLength = aLength; if (fProblem != null && fProblem instanceof AntModelProblem) { ((AntModelProblem) fProblem).setLength(aLength); } } /** * Returns a string representation of this element. */ @Override public String toString() { return "Ant Element Node: " + getLabel() + " Offset: " + getOffset() + " Length: " + getLength(); //$NON-NLS-1$//$NON-NLS-2$ //$NON-NLS-3$ } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#isErrorNode() */ @Override public boolean isErrorNode() { return fProblemSeverity == AntModelProblem.SEVERITY_ERROR || fProblemSeverity == AntModelProblem.SEVERITY_FATAL_ERROR; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#isWarningNode() */ @Override public boolean isWarningNode() { return fProblemSeverity == AntModelProblem.SEVERITY_WARNING; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#setProblemSeverity(int) */ @Override public void setProblemSeverity(int severity) { fProblemSeverity = severity; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#isExternal() */ @Override public boolean isExternal() { return fIsExternal; } /** * Sets whether this xml element is defined in an external entity. */ public void setExternal(boolean isExternal) { fIsExternal = isExternal; } /** * Returns a unique string representation of this element. The format of the string is not specified. * * @return the string representation */ @Override public String getElementPath() { if (fElementPath == null) { StringBuffer buffer = new StringBuffer(); String buildFileName = getProjectNode().getBuildFileName(); if (buildFileName != null) { buffer.append(buildFileName); } buffer.append(getParentNode() != null ? getParentNode().getElementPath() : IAntCoreConstants.EMPTY_STRING); buffer.append('/'); buffer.append(getElementIdentifier()); buffer.append('['); buffer.append(fIndex); buffer.append(']'); fElementPath = buffer.toString(); } return fElementPath; } private String getElementIdentifier() { if (fElementIdentifier == null) { StringBuffer buffer = escape(new StringBuffer(getName() != null ? getName() : IAntCoreConstants.EMPTY_STRING), '\\', "$/[]\\"); //$NON-NLS-1$ buffer.append('$'); buffer.append(escape(new StringBuffer(getLabel() != null ? getLabel() : IAntCoreConstants.EMPTY_STRING), '\\', "$/[]\\").toString()); //$NON-NLS-1$ fElementIdentifier = buffer.toString(); } return fElementIdentifier; } private StringBuffer escape(StringBuffer sb, char esc, String special) { for (int i = 0; i < sb.length(); i++) { if (special.indexOf(sb.charAt(i)) >= 0) { sb.insert(i++, esc); } } return sb; } /* * (non-Javadoc) * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object o2) { Object o1 = this; if (o1 == o2) { return true; } if (o2 == null) { return false; } if (!(o1 instanceof AntElementNode || o2 instanceof AntElementNode)) { return o2.equals(o1); } if (!(o1 instanceof AntElementNode && o2 instanceof AntElementNode)) { return false; } AntElementNode e1 = (AntElementNode) o1; AntElementNode e2 = (AntElementNode) o2; return e1.getElementPath().equals(e2.getElementPath()); } /* * (non-Javadoc) * * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return getElementPath().hashCode(); } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getSelectionLength() */ @Override public int getSelectionLength() { return fSelectionLength; } public void setSelectionLength(int selectionLength) { this.fSelectionLength = selectionLength; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getNode(int) */ @Override public AntElementNode getNode(int sourceOffset) { synchronized (this) { if (fChildNodes != null) { for (IAntElement node : fChildNodes) { AntElementNode containingNode = node.getNode(sourceOffset); if (containingNode != null) { return containingNode; } } } } if (fLength == -1 && fOffset <= sourceOffset && !isExternal()) { // this is still an open element return this; } if (fOffset <= sourceOffset && sourceOffset <= (fOffset + fLength - 2)) { return this; } return null; } public Image getImage() { int flags = 0; if (isErrorNode()) { flags = flags | AntImageDescriptor.HAS_ERRORS; } else if (isWarningNode()) { flags = flags | AntImageDescriptor.HAS_WARNINGS; } if (fImportNode != null || isExternal()) { flags = flags | AntImageDescriptor.IMPORTED; } ImageDescriptor base = getBaseImageDescriptor(); return AntUIImages.getImage(new AntImageDescriptor(base, flags)); } protected ImageDescriptor getBaseImageDescriptor() { return AntUIImages.getImageDescriptor(IAntUIConstants.IMG_TASK_PROPOSAL); } protected IAntModel getAntModel() { IAntElement parentNode = getParentNode(); while (!(parentNode instanceof AntProjectNode)) { parentNode = parentNode.getParentNode(); } if (parentNode instanceof AntProjectNode) { return ((AntProjectNode) parentNode).getAntModel(); } return null; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#setProblem(org.eclipse.ant.internal.ui.model.IProblem) */ @Override public void setProblem(IProblem problem) { fProblem = problem; } /** * @return */ public IProblem getProblem() { return fProblem; } protected void appendEntityName(StringBuffer displayName) { String path = getFilePath(); if (getImportNode() != null) { displayName.append(MessageFormat.format(AntModelMessages.AntElementNode_9, new Object[] { getImportNode().getLabel() })); } else { String entityName = getAntModel().getEntityName(path); displayName.append(MessageFormat.format(AntModelMessages.AntElementNode_9, new Object[] { entityName })); } } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getImportNode() */ @Override public AntElementNode getImportNode() { return fImportNode; } public void setImportNode(AntElementNode importNode) { fImportNode = importNode; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#hasChildren() */ @Override public boolean hasChildren() { if (fChildNodes == null) { return false; } return !fChildNodes.isEmpty(); } public void reset() { fChildNodes = null; } public void setExternalInfo(int line, int column) { fLine = line; fColumn = column; } public int[] getExternalInfo() { return new int[] { fLine, fColumn }; } /** * Return the resource that contains the definition of this Ant node. * * @return The resource that contains the definition of this ant node or <code>null</code> if that resource could not be determined (a buildfile * that is external to the workspace). */ public IFile getIFile() { if (isExternal()) { return AntUtil.getFileForLocation(fFilePath, null); } return getBuildFileResource(); } /** * Return the resource that is the main build file for this Ant node. * * @return The resource that is the main buildfile for this ant node or <code>null</code> if that resource could not be determined (a buildfile * that is external to the workspace). */ public IFile getBuildFileResource() { LocationProvider locationProvider = getAntModel().getLocationProvider(); return locationProvider.getFile(); } /* * (non-Javadoc) * * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class) */ @Override public <T> T getAdapter(Class<T> adapter) { return Platform.getAdapterManager().getAdapter(this, adapter); } /** * Returns whether this node is a structural node that should be shown in the buildfile outline. For example, an AntCommentNode would return * <code>false</code> * * @return whether this node is a structural node that should be shown in the buildfile outline */ public boolean isStructuralNode() { return true; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#collapseProjection() */ @Override public boolean collapseProjection() { return false; } public void dispose() { getAntModel().dispose(); } /** * Returns the name or path of the element referenced at the offset within the declaration of this node or <code>null</code> if no element is * referenced at the offset * * @param offset * The offset within the declaration of this node * @return <code>null</code> or the name or path of the referenced element */ public String getReferencedElement(int offset) { return null; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#getProblemMessage() */ @Override public String getProblemMessage() { return fProblemMessage; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#setProblemMessage(java.lang.String) */ @Override public void setProblemMessage(String problemMessage) { fProblemMessage = problemMessage; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#containsOccurrence(java.lang.String) */ @Override public boolean containsOccurrence(String identifier) { return false; } /** * Returns the identifier to use for matching occurrences in the Ant editor. * * @return the occurrences identifier for this node */ public String getOccurrencesIdentifier() { return getLabel(); } /** * Returns whether the supplied region can be considered as an area in this node containing a reference. * * @param region * the area to consider for finding a reference * @return whether a reference could exist in this node from the supplied region */ public boolean isRegionPotentialReference(IRegion region) { return region.getOffset() >= fOffset; } /* * (non-Javadoc) * * @see org.eclipse.ant.internal.ui.model.IAntElement#computeIdentifierOffsets(java.lang.String) */ @Override public List<Integer> computeIdentifierOffsets(String identifier) { return null; } /** * Returns whether the supplied region is from within this node's declaration identifier area * * @param region * The region to check * @return whether the region is from within this node and is the declaration of a reference. */ public boolean isFromDeclaration(IRegion region) { return false; } protected boolean checkReferenceRegion(IRegion region, String textToSearch, String attributeName) { int attributeOffset = textToSearch.indexOf(attributeName); while (attributeOffset > 0 && !Character.isWhitespace(textToSearch.charAt(attributeOffset - 1))) { attributeOffset = textToSearch.indexOf(attributeName, attributeOffset + 1); } if (attributeOffset != -1) { attributeOffset += attributeName.length(); int attributeOffsetEnd = textToSearch.indexOf('"', attributeOffset); attributeOffsetEnd = textToSearch.indexOf('"', attributeOffsetEnd + 1); return region.getOffset() >= getOffset() + attributeOffset && (region.getOffset() + region.getLength()) <= getOffset() + attributeOffsetEnd; } return false; } }